#include "ul_timer.h"
#include <sys/time.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <algorithm>
#include "ul_define.h"
#include "ul_log_adapter.h"
#include "ul_util.h"
using namespace LinuxNetworkUtility;

namespace 
{
  const int64_t MIN_TIME_ACCURACY_US = 5000;

  int64_t get_cur_time_us()
  {
    struct timeval now;
    gettimeofday(&now,NULL);
    return(now.tv_sec*1000000 + now.tv_usec);
  }
  void *ul_timer_thread(void *arg)
  {
    int err = 0;
    LinuxNetworkUtility::UlTimer *handler = NULL;
    int64_t cur_time_us = 0;
    struct timeval accuracy;
    int64_t interval_us = 0;
    int64_t round = 0;
    int64_t revise_round_interval = 0;
    if (NULL == arg)
    {
      errno = EINVAL;
      err = ERRNO2ERR(errno);
    }
    if (0 == err)
    {
      handler =  reinterpret_cast<LinuxNetworkUtility::UlTimer *>(arg);
      handler->get_accuracy(accuracy);
      interval_us = accuracy.tv_sec*1000000 + accuracy.tv_usec;
      cur_time_us = get_cur_time_us();
      handler->set_cur_time_us(cur_time_us);
      /// revise the value every one second
      revise_round_interval = 1000000/interval_us;
      if (revise_round_interval <= 0)
      {
        revise_round_interval = 1;
      }
      while (handler->is_running())
      {
        ul_usleep(interval_us);
        if (round == revise_round_interval)
        {
          round = 0;
          cur_time_us = get_cur_time_us();
        }
        else
        {
          cur_time_us += interval_us;
        }
        handler->set_cur_time_us(cur_time_us);
        handler->run_routines();
        round ++;
      }
    }
    return NULL;
  }
}

LinuxNetworkUtility::UlTimer::UlTimer()
{
  worker_thread_is_running_ = false;
  cur_time_us_ = ::get_cur_time_us();
  process_epoch_begin_us_ = cur_time_us_;
  pthread_spin_init(&spin_lock_, PTHREAD_PROCESS_PRIVATE);
  registered_timer_routine_num_ = 0;
}


void LinuxNetworkUtility::UlTimer::stop()
{
  if (worker_thread_is_running_)
  {
    worker_thread_is_running_ = false;
    pthread_join(worker_thread_,NULL);
  }
}

LinuxNetworkUtility::UlTimer::~UlTimer()
{
  stop();
  pthread_spin_lock(&spin_lock_);
  for (int32_t i = 0; i < registered_timer_routine_num_; i++)
  {
    if (routines_[i].destroy_routine_ != NULL)
    {
      routines_[i].destroy_routine_(routines_[i].arg_);
    }
  }
  memset(routines_,0x00, sizeof(routines_));
  pthread_spin_unlock(&spin_lock_);
  pthread_spin_destroy(&spin_lock_);
}




int LinuxNetworkUtility::UlTimer::init(const int64_t accuracy_us)
{
  int err = 0;
  int64_t real_accuracy_us = std::max<int64_t>(accuracy_us, MIN_TIME_ACCURACY_US);
  if (worker_thread_is_running_)
  {
    errno = EALREADY;
    UL_LOG_WARNING("class has already been initialized, double initilize");
    err = ERRNO2ERR(errno);
  }
  if (0 == err)
  {
    time_accuracy_.tv_sec = real_accuracy_us/1000000;
    time_accuracy_.tv_usec = real_accuracy_us%1000000;
    worker_thread_is_running_ = true;
    errno = pthread_create(&worker_thread_,NULL, ul_timer_thread,this);
    if (0 != errno)
    {
      UL_LOG_WARNING("fail to create worker thread [errno:%d]",errno);
      worker_thread_is_running_ = false;
      err = ERRNO2ERR(errno);
    }
  }
  if (0 != err)
  {
    worker_thread_is_running_ = false;
  }
  return err;
}

int LinuxNetworkUtility::UlTimer::register_timer_routine(ul_timer_routine_t routine, 
                                                         ul_timer_routine_t destroy_routine, 
                                                         void *arg, int64_t frenquency_us)
{
  int err = 0;
  if (NULL == routine)
  {
    UL_LOG_WARNING("argument error [routien:%p]",routine);
    errno = EINVAL;
    err = ERRNO2ERR(errno);
  }
  if (0 == err)
  {
    pthread_spin_lock(&spin_lock_);
    if (registered_timer_routine_num_ >= static_cast<int32_t>(sizeof(routines_)/sizeof(TimerRoutine)))
    {
      errno = ENOMEM;
      UL_LOG_WARNING("there is no enough space to register timer routine");
      err = ERRNO2ERR(errno);
    }
    else
    {
      routines_[registered_timer_routine_num_].arg_ = arg;
      routines_[registered_timer_routine_num_].destroy_routine_ = destroy_routine;
      routines_[registered_timer_routine_num_].routine_ = routine;
      routines_[registered_timer_routine_num_].last_exec_time_us_ = 0;
      routines_[registered_timer_routine_num_].frenquency_us_ = frenquency_us;
    }
    pthread_spin_unlock(&spin_lock_);
  }
  return err;
}

void LinuxNetworkUtility::UlTimer::unregister_timer_routine(ul_timer_routine_t routine)
{
  pthread_spin_lock(&spin_lock_);
  for (int32_t i = 0; i < registered_timer_routine_num_; i++)
  {
    if (routines_[i].routine_ == routine)
    {
      if (routines_[i].destroy_routine_ != NULL)
      {
        routines_[i].destroy_routine_(routines_[i].arg_);
      }
      memset(routines_ + i,0x00, sizeof(TimerRoutine));
    }
  }
  pthread_spin_unlock(&spin_lock_);
}

void LinuxNetworkUtility::UlTimer::run_routines()
{
  pthread_spin_lock(&spin_lock_);
  for (int32_t i = 0; i < registered_timer_routine_num_; i++)
  {
    if (routines_[i].routine_ != NULL 
        && cur_time_us_ - routines_[i].last_exec_time_us_ > routines_[i].frenquency_us_)
    {
      routines_[i].routine_(routines_[i].arg_);
    }
  }
  pthread_spin_unlock(&spin_lock_);
} 
